﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved All Rights Reserved
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Linq.Dynamic;
using System.Windows.Threading;
using System.Xml;
using FinPlusAssembler;
using FinPlusInterfaces;

namespace FinPlusBaseCore
{
    public class Pod : ObservableCollection<IBean>, IPod 
    {
        //public
        public object this[string name, string user = null] { get { return Value(name); } set { Add(name, value, user ?? Environment.UserName ); } }
        public int Id { get { return _id; } set { _id = value; } }
        public string LinkedId { get; set; }  
        public string Name { get{return _name; } set{ _name = value; } }
        public object Object { get; set; }
        public Type Type { get; set; }
        
        public string Error { get { return _error; } set { _error = value; } }
        public bool IsClosed { get; set; }
        public bool IsDirty { get; set; }
        public string ServiceKey { get; set; }
        public string Invoke { get; set; }
        public override event NotifyCollectionChangedEventHandler CollectionChanged;
        public EventHandler<BeanEventArgs> BeanUpdate { get; set; }
 
        //private
        private int _id = -1, _pos = 1;
        private IList<string> _parents = new List<string>(), _children = new List<string>();
        private SortedDictionary<int, IBean> _index = new SortedDictionary<int, IBean>();
        private string _name = Params.Unbooked.ToString(), _error = Params.Unbooked.ToString(), _user = Environment.UserName; 
        private enum Params { Message, Pod, Tag, Unbooked}
   
        //construct
        public Pod() { }

        public Pod(string type) : this(type, null, null, -1, null) { }

        public Pod(string type, string id) : this(type, id, null, -1, null) { }

        public Pod(string type, string id, string user) : this(type, id, user, -1, null) { }

        public Pod(string type, string id, string user, int tempId) : this(type, id, user, tempId, null) { }

        public Pod(string type, string id, string user, int tempId, string tag) { Initialise(type, id, user, tempId, tag); }

        public Pod(string type, string message, IPod value, int tempId)
        {
            _name = type;
            _user = Environment.UserName;
            var dateTime = DateTime.Now;
            _id = tempId;

            TryAdd(new Bean(1, tempId, PodField.Id.ToString(), tempId < 0 ? Params.Unbooked.ToString() : tempId.ToString(), dateTime, _user));
            TryAdd(new Bean(2, tempId, PodField.Type.ToString(), type, dateTime, _user));
            TryAdd(new Bean(3, tempId, PodField.TimeStamp.ToString(), DateTime.Now, dateTime, _user));
            Add(Params.Message.ToString(), message);
            Add(Params.Pod.ToString(), value);
        }

        public Pod(IEnumerable<IBean> pod)
        {
            foreach (var bean in pod.OrderBy(i => i.Index))
            {
                TryAdd(bean);
                if (bean.Name.Equals(PodField.Id.ToString()))
                    int.TryParse(bean.ToString(), out _id);
            }
        }

        public Pod(object[,] array) { Initialise(array);}

        public Pod(IMessage message)
        {
            IsClosed = message.ActionType == ActionType.Close ? true : false;
            Initialise((object[,])message.Value);
        }

        public Pod(int id, XmlElement xml, IDictionary<string, string> replace) { this.FromXml(ref _pos, id, xml, replace); }

        //public
        public IPod Create(int id) 
        {
            var pod = new Pod();
            pod.Id = id;
            return pod;
        }

        public IPod Clone(int id)
        {
            var clone = new Pod(this.Name, null, _user, id);
            clone.Id = id;

            foreach (var bean in _index.Values)
                clone.TryAdd(bean.Clone(id));

            clone.Get(PodField.Id.ToString()).Update(id);
            clone.IsClosed = IsClosed;

            return clone;
        }

        public void Update(IPod values)
        {
            foreach (var value in values)
            {
                var bean = Get(value.Name);
                if(value.TimeStamp> bean.TimeStamp )
                    bean.Update(value);
            }
        }

        public void Refresh()
        {
            foreach (var bean in this)
                bean.Refresh();
        }

        public object Value(string name, object defaultValue = null)
        {
            foreach (IBean bean in this)
                if (bean.Name == name)
                    return bean.Object;

            return defaultValue;
        }

        public IBean Get<T>(string name, T defaultValue, PropertyChangedEventHandler changeEventHander = null)
        {
            lock (this)
            {
                var bean = Get(name, changeEventHander);

                if (bean == null)
                    bean = Add(name, defaultValue, _user, Id);

                if (changeEventHander != null)
                    bean.PropertyChanged += changeEventHander;

                return bean;
            }
        }

        public IBean Get(string name, PropertyChangedEventHandler changeEventHander = null)
        {
            IBean b = null;

            foreach (IBean bean in this)
                if (bean.Name == name)
                {
                    b = bean;
                    break;
                }

            if (b != null && changeEventHander != null)
                b.PropertyChanged += changeEventHander;

            return b;
        }

        public IBean Add(string name, object value, string user = "", int id = -1) { return Add(name, value, user, id, NextKey(), DateTime.Now); }

        public IBean Add(string name, object value, string user, int id, int index, DateTime timeStamp)
        {
            var bean = new Bean(index, id, name, value, timeStamp, user);

            if (!this.TryAdd(bean))
                throw new BeanException(string.Format("unable to add bean to pod {0}|{1}|{2}|{3}", name, index, id, user));
            
            return bean;
        }

        public void Add(IDictionary<string, string> beans)
        {
            foreach (var kvp in beans)
            {
                switch (kvp.Value.EnumParse<BeanType>(BeanType.Unknown))
                {
                    case BeanType.String: this[kvp.Key] = BeanType.Unknown.ToString(); break;
                    case BeanType.DateTime: this[kvp.Key] = DateTime.MinValue; break;
                    case BeanType.Double: this[kvp.Key] = 0.0; break;
                    case BeanType.Int32:
                    case BeanType.Int: this[kvp.Key] = 0; break;
                    case BeanType.Pod: this[kvp.Key] = new Pod(); break;
                    case BeanType.Unknown: this[kvp.Key] = BeanType.Unknown.ToString(); break;
                }
            }
        }

        public bool TryAdd(IBean bean, bool withHistory = false, bool newKey = false)
        {
            try
            {
                if (newKey || bean.Index < 0) 
                    bean.Index = NextKey();

                if (!History(bean, withHistory))
                    return false;

                bean.PropertyChanged += Bean_Changed;
                
                base.Add(bean);
                _index[bean.Index] = bean;

                if (bean.Name.Equals(PodField.Type.ToString()))
                    _name = bean.ToString();

                return true;
            }
            catch
            {
                return false;
            }
        }

        public new void Remove(IBean bean)
        {
            base.Remove(bean);
            if (_index.ContainsKey(bean.Index))
                _index.Remove(bean.Index);
        }

        public bool IsEqual(IPod value)
        {
            var first = string.Join(",", this.Select(f => f.ToString()).ToArray());
            var second = string.Join(",", value.Select(f => f.ToString()).ToArray());

            return first.Equals(second);
        }

        public bool IsMatch(string where)
        {
            if (string.IsNullOrEmpty(where)) 
                return true;

            return this.AsQueryable().Where(where.Replace("'", "\"")).Any();
        }

        public override string ToString() {return ToArray().StringFrom2d();}

        public object[,] ToArray()
        {
            var a = new object[this.Count(), 7];
            int i = 0;

            foreach (var bean in _index.Values)
            {
                a[i, 0] = bean.Id;
                a[i, 1] = bean.Name;
                a[i, 2] = bean.ToString();
                a[i, 3] = bean.Type;
                a[i, 4] = bean.TimeStamp.ToOADate();
                a[i, 5] = bean.Closed.ToOADate();
                a[i, 6] = bean.User;
                i++;
            }

            return a;
        }

        public void FromXml(ref int pos, int id, XmlElement xml, IDictionary<string, string> replace)
        {
            Id = id;

            if (xml.ChildNodes.Count > 0)
                AddXml(xml, ref pos, replace);

            if (xml.Attributes.Count > 0)
                AddXml(xml.Attributes, ref pos, replace);
        }

        public void AddXml(XmlElement xmlBeans, ref int pos, IDictionary<string, string> replace)
        {
            foreach (XmlElement xmlBean in xmlBeans)
                if (xmlBean.Name.Equals(Common.Bean.ToString()))
                    TryAdd(new Bean(pos++, Id, xmlBean, replace));
        }

        public void AddXml(XmlAttributeCollection xmlBeans, ref int pos, IDictionary<string, string> replace)
        {
            foreach (XmlAttribute xmlBean in xmlBeans)
                TryAdd(new Bean(pos++, Id, xmlBean.Name, xmlBean.Value.Replace(replace), DateTime.MinValue, "Unknown"));
        }

        public void ToXml(XmlDocument xmlDoc, string node)
        {
            var xmlNode = xmlDoc.SelectSingleNode(node, null);
            var xmlPodNode = xmlDoc.CreateNode(XmlNodeType.Element, "Pod", null);

            xmlNode.AppendChild(xmlPodNode);
            foreach (var bean in this)
                if (bean.Name.EnumIsDefined<PodField>())
                {
                    var attribute = xmlDoc.CreateAttribute(bean.Name);
                    attribute.Value = bean.ToString();
                    xmlPodNode.Attributes.Append(attribute);
                }
                else
                    xmlPodNode.AppendChild(bean.ToXml(xmlDoc));
        }

        //private
        private void Initialise(string name, string id = null, string user = null, int tempId = -1, string tag = null)
        {
            _name = name;
            _user = user ?? Environment.UserName;
            var dateTime = id == null ? DateTime.MinValue : DateTime.Now;
            
            TryAdd(new Bean(1, tempId, PodField.Id.ToString(), id ?? Params.Unbooked.ToString(), dateTime, _user));
            TryAdd(new Bean(2, tempId, PodField.Type.ToString(), name, dateTime, _user));
            TryAdd(new Bean(3, tempId, PodField.TimeStamp.ToString(), DateTime.MinValue, dateTime, _user));
            if (tag != null)
                TryAdd(new Bean(4, tempId, Params.Tag.ToString(), tag, dateTime, _user));
        }

        private void Initialise(object[,] array)
        {
            var r = array.GetUpperBound(0);
            if (r.Equals(0)) 
                return;

            for (int i = 0; i <= r; i++)
            {
                var id = int.Parse(array[i, 0].ToString());
                var name = array[i, 1].ToString();
                var type = IOC.FindType(array[i, 3].ToString());
                var value = type.ChangeType(array[i, 2].ToString());
                var user = (array[i, 5] ?? "").ToString();
                var timeStamp = (DateTime)typeof(DateTime).ChangeType(array[i, 4].ToString());

                var bean = new Bean(i, id, name, value, timeStamp, user);
                
                if (bean.Name.Equals(PodField.Type.ToString()))
                {
                    _name = bean.ToString(); 
                    _id = bean.Id;
                }

                this.TryAdd(bean);
            }
        }

        private string Value(string name)
        {
            foreach (IBean i in this)
                if (i.Name.Equals(name))
                    return i.ToString();

            return string.Empty;
        }

        private bool History(IBean bean, bool withHistory)
        {
            if (_index.ContainsKey(bean.Index))
            {
                var beanExisting = _index[bean.Index];
                if (!withHistory && beanExisting.TimeStamp < bean.TimeStamp)
                    Remove(beanExisting); 
                else
                    return false; 
            }

            return true;
        }

        private int NextKey() { return _index.Count() < 1 ? 0 : _index.Max(kvp => kvp.Key) + 1; }

        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs a)
        {
            using (BlockReentrancy())
            {
                var collectionChanged = this.CollectionChanged;
                if (collectionChanged == null) return;
                foreach (NotifyCollectionChangedEventHandler handler in collectionChanged.GetInvocationList())
                {
                    var dispObj = (DispatcherObject)handler.Target;
                    if (dispObj != null)
                    {
                        var disp = dispObj.Dispatcher;
                        if (disp != null && !disp.CheckAccess())
                        {
                            disp.BeginInvoke((Action)(() => handler.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))), DispatcherPriority.DataBind);
                            continue;
                        }
                    }
                    handler.Invoke(this, a);
                }
            }
        }

        //events
        private void Bean_Changed(object s, PropertyChangedEventArgs a)
        {
           if(BeanUpdate != null)
               BeanUpdate.Invoke(this, new BeanEventArgs((IBean)s, ChangeType.Altered));
        }
        private void Bean_Update(object s, PropertyChangedEventArgs a)
        {
            var dPod = (IDPod)s;
            var bean = Get(a.PropertyName);
            if(dPod.GetTimeStamp(a.PropertyName) > bean.TimeStamp)
                bean.Update(dPod.GetValue(a.PropertyName), dPod.GetTimeStamp(a.PropertyName), null, true);
        }
    }
}























































































































//Copyright2013<jnrton>AllRightsReserved 
//Contact j.nrton@finpus